﻿using Orchard;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Security;
using Orchard.Services;
using Orchard.Mvc;
using System.Web.Mvc;
using NineSeas.Services;
using NineSeas.Models;
using NineSeas.ViewModels;
using System;
using System.Linq;
using Orchard.Themes;
using Orchard.FileSystems.Media;
using Orchard.Data;
using Orchard.Tags.Models;
using Orchard.DisplayManagement;
using Orchard.Tags.Helpers;
using Orchard.Tags.Services;
using Orchard.UI.Notify;
using System.Text;
using Contrib.Taxonomies.Services;
using NineSeas.Extensions;
using System.Collections.Generic;
using Contrib.Taxonomies.Models;
using Orchard.PublishLater.Services;
using System.Globalization;
using Orchard.PublishLater.Models;
using Orchard.Tasks.Scheduling;
namespace NineSeas.Controllers
{
    public class BooksController : Controller, IUpdateModel
    {
        private static readonly char[] _disalowedChars = new[] { '<', '>', '*', '%', ':', '&', '\\', '"', '|' };
        private readonly IAuthenticationService _authenticationService;
        private readonly IAuthorizationService _authorizationService;
        private readonly IOrchardServices _services;
        private readonly IClock _clock;
        private readonly IBookService _bookService;
        private readonly IChapterService _chapterService;
        private readonly IStorageProvider _storageProvider;
        private readonly IAuthorService _authorService;
        private readonly IRepository<BookPartRecord> _bookRepository;
        private readonly ITaxonomyService _taxonomyService;
        private readonly ITagService _tagService;
        private readonly INotifier _notifier;
        private readonly IPublishingTaskManager _publishingTaskManager;
        private readonly Lazy<CultureInfo> _cultureInfo;
        private Localizer T { get; set; }
        private dynamic Shape { get; set; }

        public BooksController(IOrchardServices services,
                IAuthenticationService authenticationService,
                IAuthorizationService authorizationService,
                IClock clock,
                IBookService bookService,
                IShapeFactory shapeFactory, 
                IRepository<BookPartRecord> bookRepository,
                IAuthorService authorService,
                IChapterService chapterService, 
                ITagService tagService, 
                INotifier notifier,
                IStorageProvider storageProvider,
                ITaxonomyService taxonomyService,
                IPublishingTaskManager publishingTaskManager)
        {
            Shape = shapeFactory;
            this._services = services;
            this._authenticationService = authenticationService;
            this._authorizationService = authorizationService;
            this._clock = clock;
            this._bookService = bookService;
            this._chapterService = chapterService;
            this._storageProvider = storageProvider;
            this._authorService = authorService;
            this._bookRepository = bookRepository;
            this._taxonomyService = taxonomyService;
            this._publishingTaskManager = publishingTaskManager;
            _tagService = tagService;
            _notifier = notifier;
            T = NullLocalizer.Instance;

            // initializing the culture info lazy initializer
            _cultureInfo = new Lazy<CultureInfo>(() => CultureInfo.GetCultureInfo(_services.WorkContext.CurrentCulture));
        }


        protected string Prefix
        {
            get { return "Book"; }
        }

        public ActionResult Index(BookSearchViewModel search, PaginationInfo paging)
        {
            if (!_services.Authorizer.Authorize(Permissions.AddBooks, T("You do not have the permission to this page.")))
            {
                return Redirect("~/");
            }
            var user = _authenticationService.GetAuthenticatedUser();

            search.AuthorId = user.Id;

            search.SetOptions(new BookSearchViewModel.SearchOptions {
                ForEdit = true
            });

            var books = _bookService.Search(search, paging, VersionOptions.Latest);
                        
            return new ShapeResult(this, _services.New.Authoring_Books_Index(
                    search: search,
                    Books: books,
                    Paging: paging
                ));
        }

        [Themed]
        public ActionResult View(int id, PaginationInfo paging)
        {
            BookPart bookPart = null;
            dynamic chapters = null;
            dynamic tagsPart = null;
            if (id >= 0)
            {
                bookPart = _services.ContentManager.Get<BookPart>(id);
                chapters = _chapterService.GetByBookId(id, paging);
                tagsPart = bookPart.As<TagsPart>();
            }
            return new ShapeResult(this, _services.New.Books_Book_View(
                    Book: bookPart,
                    //Tags: tagsPart != null ? _services.ContentManager.bu : null,
                    Chapters: chapters,
                    Paging: paging
                ));
        }

        [Themed]
        public ActionResult Edit(int? id)
        {
            if (!_services.Authorizer.Authorize(Permissions.AddBooks, T("You do not have the permission to this page.")))
            {
                return Redirect("~/");
            }
            var user = _authenticationService.GetAuthenticatedUser();

            var bookViewModel = new BookViewModel();
            bool isNew = true;
            TagsPart tagsPart = null;
            string firstChapterLink = String.Empty;
            if (id.HasValue && id.Value >= 0)
            {
                var bookPart = _services.ContentManager.Get<BookPart>(id.Value, VersionOptions.DraftRequired);

                if (bookPart != null)
                {
                    if (bookPart.AuthorId != user.Id 
                        && !_services.Authorizer.Authorize(Permissions.ManageBooks, T("You do not have the permission to edit other's books.")))
                    {
                        return Redirect("~/");
                    }

                    bookViewModel.Id = bookPart.Id;
                    bookViewModel.Name = bookPart.Name;
                    bookViewModel.NameOnUrl = bookPart.NameOnUrl;
                    bookViewModel.Introduction = bookPart.Introduction;
                    bookViewModel.ImageUrl = bookPart.ImageUrl;
                    bookViewModel.ImageStoragePath = bookPart.ImageStoragePath;

                    var categoryStrings = new StringBuilder();
                    bookViewModel.Categories = bookPart.Categories.Terms.Value;

                    isNew = false;

                    tagsPart = bookPart.As<TagsPart>();
                }

                var firstChapter = _chapterService.GetFirst(id.Value);

                firstChapterLink = firstChapter != null ? Url.Action("Edit", "Chapter", new { id = firstChapter.Id })
                    : Url.Action("Create", "Chapter", new { bookId = bookPart.Id });
            }

            var taxonomy = _taxonomyService.GetTaxonomyByName(Constants.Taxonomies.Categories);
            
            return new ShapeResult(this, _services.New.Authoring_Book_Edit(
                    IsNew: isNew,
                    User: user,
                    Book: bookViewModel,
                    Categories: _taxonomyService.GetTerms(taxonomy.Id),
                    FirstChapterLink: firstChapterLink,
                    Tags: tagsPart != null ? String.Join(", ", tagsPart.CurrentTags.Select((t, i) => t.TagName).ToArray()) : null,
                    ImageUrl: String.IsNullOrWhiteSpace(bookViewModel.ImageUrl) ?
                                Url.Content("~/Modules/NineSeas/Images/placeholder-book.png")
                                : bookViewModel.ImageUrl
                ));
        }

        [HttpPost, ActionName("Edit")]
        [ValidateInput(false)]
        public ActionResult EditPost(BookViewModel bookViewModel)
        {
            if (!_services.Authorizer.Authorize(Permissions.AddBooks))
            {
                return Json(new { success = false, message = T("You do not have the permission to edit this book.").Text });
            }

            var user = _authenticationService.GetAuthenticatedUser();
            
            // validation
            var validationResult = _bookService.ValidateBook(bookViewModel); 
            if ( validationResult != BookValidationResult.Success) // only one case: BookValidationResult.NameOnUrlExists
            {
                var message = T("The Name On Url field must be unique. The value you entered has been used.").Text;
                return Json(new { success = false, message = message });
            }
            
            Action<BookPart> filler = b => {
                b.Name = bookViewModel.Name;
                b.NameOnUrl = bookViewModel.NameOnUrl;
                b.Introduction = bookViewModel.Introduction;
                b.LastUpdatedDate = _clock.UtcNow;

                if (!String.IsNullOrEmpty(bookViewModel.ImageStoragePath) 
                    && String.Compare(b.ImageStoragePath, bookViewModel.ImageStoragePath) != 0)
                {
                    b.ImageUrl = _storageProvider.GetPublicUrl(bookViewModel.ImageStoragePath);
                }

                b.ImageStoragePath = bookViewModel.ImageStoragePath;
            };

            BookPart bookPart;
            if (bookViewModel.Id.HasValue && bookViewModel.Id >= 0)
            {
                bookPart = _services.ContentManager.Get<BookPart>(bookViewModel.Id.Value, VersionOptions.DraftRequired);//_bookService.GetById(bookViewModel.Id.Value);

                if (bookPart.ContentItem.VersionRecord.Published)
                {
                    _services.ContentManager.Create(bookPart, VersionOptions.Draft);
                }

                if (bookPart != null)
                {
                    filler.Invoke(bookPart);
                }
            }
            else
            {
                bookPart = _services.ContentManager.Create<BookPart>("Book", VersionOptions.Draft, b => {
                    b.AuthorId = user.Id;
                    b.CreatedDate = _clock.UtcNow;
                    filler.Invoke(b);
                });
            }

            if (bookPart != null)
            {
                // tags and categories
                SaveTags(bookPart, TagHelpers.ParseCommaSeparatedTagNames(bookViewModel.Tags));
                SaveCategories(bookPart, bookViewModel.CategoryIds);
                
                var profile = user.As<AuthorProfilePart>();
                if (profile != null)
                {
                    profile.BookCount = _bookRepository.Count(b => b.AuthorId == profile.Id);
                    _services.ContentManager.UpdateEditor(profile, this);
                }

                ProcessPublication(bookPart, bookViewModel);
            }

            ModelState modelState = ModelState[Prefix];
            ModelErrorCollection modelErrors = (modelState == null) ? null : modelState.Errors;
            ModelError modelError = (((modelErrors == null) || (modelErrors.Count == 0)) ? null : modelErrors.FirstOrDefault(m => !String.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0]);

            if (modelError == null)
            {
                return Json(new { success = true, id = bookPart.Id });
            }
            else
            {

                return Json(new { success = false, message = modelError.ErrorMessage });
            }
            //return Json(new { success = true, id = bookPart.Id });
        }

        public ActionResult Delete(int id)
        {
            var user = _authenticationService.GetAuthenticatedUser();
            if (_authenticationService.GetAuthenticatedUser() == null)
                throw new OrchardSecurityException(T("Login required"));

            var bookPart = _services.ContentManager.Get<BookPart>(id);

            if (bookPart != null
                && (bookPart.AuthorId == user.Id || _authorizationService.TryCheckAccess(Permissions.ManageBooks, user, bookPart)))
            {
                _services.ContentManager.Remove(bookPart.ContentItem);
            }
            else
            {
                throw new OrchardSecurityException(T("You don't have permission to perform this action."));
            }


            if (!String.IsNullOrEmpty(Request["ReturnUrl"]))
            {
                return Redirect(Request["ReturnUrl"]);
            }
            else
            {
                return RedirectToAction("Index", "Authoring");
            }
        }

        #region Private Methods
        private void ProcessPublication(BookPart bookPart, BookViewModel model)
        {
            if (Constants.SaveTypes.PublishNow.Equals(model.SaveType, StringComparison.CurrentCultureIgnoreCase))
            {
                _services.ContentManager.Publish(bookPart.ContentItem);
            }
            else if (Constants.SaveTypes.PublishLater.Equals(model.SaveType, StringComparison.CurrentCultureIgnoreCase))
            {
                var part = bookPart.As<PublishLaterPart>();
                if (!string.IsNullOrWhiteSpace(model.ScheduledPublishDate) && !string.IsNullOrWhiteSpace(model.ScheduledPublishTime))
                {
                    DateTime scheduled;

                    string parseDateTime = String.Concat(model.ScheduledPublishDate, " ", model.ScheduledPublishTime);

                    // use current culture
                    if (DateTime.TryParse(parseDateTime, _cultureInfo.Value, DateTimeStyles.None, out scheduled))
                    {

                        // the date time is entered locally for the configured timezone
                        var timeZone = _services.WorkContext.CurrentTimeZone;

                        var scheduledPublishUtc = part.ScheduledPublishUtc.Value = TimeZoneInfo.ConvertTimeToUtc(scheduled, timeZone);

                        if (scheduledPublishUtc < _clock.UtcNow)
                        {
                            ModelState.AddModelError(Prefix, T("You cannot schedule a publishing date in the past").Text);
                        }
                        else
                        {
                            _publishingTaskManager.Publish(part.ContentItem, scheduledPublishUtc.Value);
                        }
                    }
                    else
                    {
                        ModelState.AddModelError(Prefix, T("{0} is an invalid date and time", parseDateTime).Text);
                    }
                }
                else if (!string.IsNullOrWhiteSpace(model.ScheduledPublishDate) || !string.IsNullOrWhiteSpace(model.ScheduledPublishTime))
                {
                    ModelState.AddModelError(Prefix, T("Both the date and time need to be specified for when this is to be published. If you don't want to schedule publishing then click Save or Publish Now.").Text);
                }
            }
            else
            {
                // do nothing, since we are saving draft
            }
        }

        private void SaveTags(BookPart bookPart, List<string> tagNames)
        {
            var tagsPart = bookPart.As<TagsPart>();
            // as the tag names are used in the route directly, prevent them from having ASP.NET disallowed chars
            // c.f., http://www.hanselman.com/blog/ExperimentsInWackinessAllowingPercentsAnglebracketsAndOtherNaughtyThingsInTheASPNETIISRequestURL.aspx

            var disallowedTags = tagNames.Where(x => _disalowedChars.Intersect(x).Any()).ToList();

            if (disallowedTags.Any())
            {
                ModelState.AddModelError(Prefix, T("The tags \"{0}\" could not be added because they contain forbidden chars: {1}", String.Join(", ", disallowedTags), String.Join(", ", _disalowedChars)).ToString());
                tagNames = tagNames.Where(x => !disallowedTags.Contains(x)).ToList();
            }

            if (tagsPart != null && tagsPart.ContentItem.Id != 0)
            {
                _tagService.UpdateTagsForContentItem(tagsPart.ContentItem, tagNames);
            }
        }

        private void SaveCategories(BookPart bookPart, string categoryIdsString)
        {
            var categoryIds = new List<int>();

            if (!String.IsNullOrWhiteSpace(categoryIdsString) && categoryIdsString.Contains(','))
            {
                var idInt = -1;
                foreach (var id in categoryIdsString.Split(','))
                {
                    if (!String.IsNullOrEmpty(id) && Int32.TryParse(id.Trim(), out idInt))
                    {
                        categoryIds.Add(idInt);
                    }
                }
            }

            IEnumerable<TermPart> terms = null;
            if (categoryIds.Count > 0)
            {
                terms = _services.ContentManager
                        .Query<TermPart, TermPartRecord>()
                        .Where(t => categoryIds.Contains(t.Id))
                        .List();
            }
            else
            {
                terms = new List<TermPart>();
            }
            _taxonomyService.UpdateTerms(bookPart.ContentItem, terms, bookPart.Categories.Name);
        }

        #endregion

        #region IUpdateModel implementations
        bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
        {
            return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
        }

        void IUpdateModel.AddModelError(string key, LocalizedString errorMessage)
        {
            ModelState.AddModelError(key, errorMessage.ToString());
        }
        #endregion
    }
}